From 0e12677eba20e54456a282451ce50d7064c3d620 Mon Sep 17 00:00:00 2001 From: oliskoli Date: Sun, 20 May 2007 20:40:02 +0000 Subject: [PATCH] garmin_poi: Add gpi writer code. --- Makefile.in | 3 +- garmin_poi.c | 470 +++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 425 insertions(+), 48 deletions(-) diff --git a/Makefile.in b/Makefile.in index db70f95fe..479801a09 100644 --- a/Makefile.in +++ b/Makefile.in @@ -413,7 +413,8 @@ garmin_fs.o: garmin_fs.c defs.h config.h queue.h gbtypes.h zlib/zlib.h \ jeeps/gpsapp.h jeeps/gpsprot.h jeeps/gpscom.h jeeps/gpsfmt.h \ jeeps/gpsmath.h jeeps/gpsmem.h jeeps/gpsrqst.h jeeps/gpsinput.h \ jeeps/gpsproj.h garmin_tables.h -garmin_poi.o: garmin_poi.c defs.h config.h queue.h gbtypes.h jeeps/gpsmath.h +garmin_poi.o: garmin_poi.c garmin_poi.h defs.h config.h queue.h gbtypes.h \ + cet_util.h jeeps/gpsmath.h garmin_tables.o: garmin_tables.c garmin_tables.h defs.h config.h queue.h \ gbtypes.h zlib/zlib.h zlib/zconf.h gbfile.h cet.h cet_util.h inifile.h \ jeeps/gpsmath.h diff --git a/garmin_poi.c b/garmin_poi.c index 567b5cc81..30acc74aa 100644 --- a/garmin_poi.c +++ b/garmin_poi.c @@ -24,17 +24,19 @@ History: * 2007/05/18: initial release (only a reader) + * 2007/05/20: added writer code with embedded bitmap ToDo: * Display mode ("Symbol & Name") * decode speed/proximity * support category from GMSD "Garmin Special Data" - * ... and the writer */ #include "defs.h" +#include "cet_util.h" #include "jeeps/gpsmath.h" +#include "garmin_poi.h" #include #include #include @@ -42,27 +44,44 @@ #define MYNAME "garmin_poi" +#define GPI_DBG 1 +#undef GPI_DBG + #define DEFAULT_ICON "Waypoint" +static char *opt_cat, *opt_pos, *opt_notes, *opt_hide, *opt_descr; + static arglist_t garmin_poi_args[] = { + {"category", &opt_cat, "Default category on output", + "My points", ARGTYPE_STRING, ARG_NOMINMAX}, + {"hide", &opt_hide, "Don't show gpi bitmap on device", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX}, + {"descr", &opt_descr, "Write description to address field", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX}, + {"notes", &opt_notes, "Write notes to address field", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX}, + {"position", &opt_pos, "Write position to address field", + NULL, ARGTYPE_BOOL, ARG_NOMINMAX}, ARG_TERMINATOR }; typedef struct { + int D2; char S3[9]; /* "GRMRECnn" */ time_t crdate; /* creation date and time */ char POI[4]; /* "POI" */ char S8[3]; - gbint32 codepage; /* code-page, i.e. 1252 */ char *group; char *category; } gpi_data_t; -static gbfile *fin; +static gbfile *fin, *fout; +static gbint32 codepage; /* code-page, i.e. 1252 */ static gpi_data_t *dt; - -#define GPI_DBG 1 -#undef GPI_DBG +static queue wptq; +static int all_points_sz; +static bounds bds; +static short_handle short_h; #ifdef GPI_DBG # define PP printf("@%1$6x (%1$8d): ", gbftell(fin)) @@ -70,6 +89,33 @@ static gpi_data_t *dt; # define PP #endif +#ifdef GPI_DBG +static void +store_bitmap(const char *filename, int sz) +{ + gbfile *fout; + + fout = gbfopen_le(filename, "wb", MYNAME); + + while (! gbfeof(fin)) { + char buff[256]; + int len; + + len = sizeof(buff); + if (len > sz) len = sz; + sz -= len; + len = gbfread(buff, 1, len, fin); + gbfwrite(buff, 1, len, fout); + if (sz <= 0) break; + } + gbfclose(fout); +} +#endif + +/******************************************************************************* +* %%% gpi reader %%% * +*******************************************************************************/ + /* read a string with embedded "EN" header */ static char * read_string(const int *sz) @@ -112,7 +158,7 @@ read_header(void) i = gbfgetint32(fin); if (i != 0) i = gbfgetint32(fin); - (void) gbfgetint32(fin); + dt->D2 = gbfgetint32(fin); gbfread(&dt->S3, 1, sizeof(dt->S3) - 1, fin); /* GRMRECnn */ if (strncmp(dt->S3, "GRMREC", 6) != 0) @@ -143,7 +189,7 @@ read_header(void) for (i = 0; i < 3; i++) (void)gbfgetc(fin); gbfread(&dt->S8, 1, sizeof(dt->S8) - 1, fin); - dt->codepage = gbfgetint32(fin); + codepage = gbfgetint32(fin); #ifdef GPI_DBG PP; @@ -166,8 +212,11 @@ read_poi(const int sz) PP; printf("> reading poi (size %d)\n", sz); #endif - (void) gbfgetint32(fin); /* sub-header size */ - + PP; + len = gbfgetint32(fin); /* sub-header size */ +#if GPI_DBG + printf("poi sublen = %1$d (0x%1$x)\n", len); +#endif pos = gbftell(fin); wpt = waypt_new(); @@ -207,16 +256,18 @@ read_poi(const int sz) static void read_poi_list(const int sz) { - int pos; + int pos, i; + pos = gbftell(fin); #ifdef GPI_DBG PP; - printf("> reading poi list\n"); + printf("> reading poi list (-> %1$x / %1$d )\n", pos + sz); #endif - pos = gbftell(fin); - - (void) gbfgetint32(fin); /* mostly 23 (0x17) */ - + PP; + i = gbfgetint32(fin); /* mostly 23 (0x17) */ +#ifdef GPI_DBG + printf("list sublen = %1$d (0x%1$x)\n", i); +#endif (void) gbfgetint32(fin); /* max-lat */ (void) gbfgetint32(fin); /* max-lon */ (void) gbfgetint32(fin); /* min-lat */ @@ -244,14 +295,19 @@ read_poi_group(const int sz, const int tag) { int len, pos; + pos = gbftell(fin); #ifdef GPI_DBG PP; - printf("> reading poi group\n"); + printf("> reading poi group (-> %1$x / %1$d)\n", pos + sz); #endif - pos = gbftell(fin); - if (tag == 0x80009) { - (void) gbfgetint32(fin); /* ? offset to category data ? */ + int subsz; + + PP; + subsz = gbfgetint32(fin); /* ? offset to category data ? */ +#ifdef GPI_DBG + printf("group sublen = %d (-> %x / %d)\n", subsz, pos + subsz + 4, pos + subsz + 4); +#endif } len = gbfgetint32(fin); /* size of group string */ @@ -278,6 +334,10 @@ static int read_tag(const char *caller, const int tag, waypoint *wpt) { int pos, sz, len; + char *str; +#ifdef GPI_DBG + int subtag; +#endif sz = gbfgetint32(fin); pos = gbftell(fin); @@ -294,7 +354,10 @@ read_tag(const char *caller, const int tag, waypoint *wpt) case 0x6: /* size = 2 ? */ break; - case 0x5: /* group bitmap (BMP: 22x22/256) */ + case 0x5: /* group bitmap (BMP: <= 24x24) */ +#ifdef GPI_DBG + // store_bitmap("gpi_bitmap", sz); +#endif break; case 0x7: /* category */ @@ -308,28 +371,34 @@ read_tag(const char *caller, const int tag, waypoint *wpt) #endif break; - case 0xa: /* notes */ + case 0xa: /* description */ len = gbfgetint32(fin); PP; - wpt->notes = read_string(&len); + wpt->description = read_string(&len); #ifdef GPI_DBG - printf("Notes: \"%s\"\n", wpt->notes); + printf("Description: \"%s\"\n", wpt->description); #endif break; - case 0xe: /* ? also notes ? */ - len = gbfgetint32(fin); + case 0xe: /* ? notes ? */ (void) gbfgetc(fin); - - len = sz - 5; - wpt->description = xmalloc(len + 1); - wpt->description[len] = '\0'; - PP; - gbfread(wpt->description, 1, len, fin); + if (dt->D2 == 0x1D) { + len = gbfgetint32(fin); + PP; + str = read_string(NULL); + } + else { + len = gbfgetint16(fin); + str = xmalloc(len + 1); + str[len] = '\0'; + PP; + gbfread(str, 1, len, fin); + } #ifdef GPI_DBG - printf("Descr: \"%s\"\n", wpt->description); + printf("Notes: \"%s\"\n", str); #endif - + if (wpt->description) wpt->notes = str; + else wpt->description = str; break; case 0x80002: @@ -345,12 +414,27 @@ read_tag(const char *caller, const int tag, waypoint *wpt) read_poi_group(sz, tag); break; - case 0x8000b: /* phone-number */ + case 0x8000b: /* address (street/city...) */ + gbfseek(fin, sz, SEEK_CUR); /* ToDo */ break; - case 0x8000c: /* address (street/city...) */ - /* ToDo */ + case 0x8000c: /* phone-number */ +#ifdef GPI_DBG + PP; + subtag = gbfgetint32(fin); + printf("phone-number tag %d\n", subtag); + (void) gbfgetint16(fin); /* unknown / ? phone/fax/mobil ? */ + PP; + len = gbfgetint16(fin); + printf("phone-number len %d\n", len); + str = xmalloc(len + 1); + str[len] = '\0'; + PP; + gbfread(str, 1, len, fin); + printf("phone-number \"%s\"\n", str); + xfree(str); +#endif break; case 0x80012: /* ? sounds / images ? */ @@ -364,6 +448,231 @@ read_tag(const char *caller, const int tag, waypoint *wpt) return 1; } +/******************************************************************************* +* %%% gpi writer %%% * +*******************************************************************************/ + +static void +write_string(const char *str) +{ + int len = strlen(str); + + gbfwrite("EN", 1, 2, fout); + gbfputint16(len, fout); + gbfwrite(str, 1, len, fout); +} + + +static char +compare_strings(const char *s1, const char *s2) +{ + if (s1 == s2) return 0; + else if (s1) { + if (s2) return strcmp(s1, s2); + else return 1; + } + else return 1; +} + + +static void +write_header(void) +{ + struct tm tm; + time_t time; + + tm = *gmtime(&gpsbabel_now); + tm.tm_year -= 20; + time = mkgmtime(&tm); + time += SECONDS_PER_DAY; + + gbfputint32(0, fout); + gbfputint32(0x16, fout); + gbfwrite("GRMREC00", 1, 8, fout); + gbfputint32(time, fout); + gbfputint16(0, fout); + gbfputint16(6, fout); + gbfwrite("my.gpi", 1, 6, fout); + gbfputint32(1, fout); + gbfputint32(0xc, fout); + gbfwrite("POI", 1, 3, fout); + gbfputc(0, fout); + gbfputc(0, fout); + gbfputc(0, fout); + gbfwrite("00", 1, 2, fout); + gbfputint32(codepage, fout); +} + +static void +enum_waypt_cb(const waypoint *ref) +{ + waypoint *wpt; + char *str; + queue *elem, *tmp; + + QUEUE_FOR_EACH(&wptq, elem, tmp) { + waypoint *cmp = (waypoint *) elem; + + if ((compare_strings(cmp->shortname, ref->shortname) == 0) && + (cmp->latitude == ref->latitude) && + (cmp->longitude == ref->longitude) && + (compare_strings(cmp->description, ref->description) == 0)) return; + } + + wpt = waypt_dupe(ref); + + ENQUEUE_TAIL(&wptq, &wpt->Q); + + str = mkshort(short_h, wpt->shortname); + xfree(wpt->shortname); + wpt->shortname = str; + + all_points_sz += 12; /* tag/sz/sub-sz */ + all_points_sz += 19; /* poi fixed size */ + all_points_sz += strlen(wpt->shortname); + all_points_sz += 10; /* tag(4) */ + + str = NULL; + if (opt_descr) { + if (wpt->description && *wpt->description) + str = xstrdup(wpt->description); + } + else if (opt_notes) { + if (wpt->notes && *wpt->notes) + str = xstrdup(wpt->notes); + } + else if (opt_pos) + str = pretty_deg_format(wpt->latitude, wpt->longitude, 's', 0); + + if (str) { /* this will be stored into street-address field */ + all_points_sz += 22 + strlen(str); + wpt->extra_data = str; + } + + str = wpt->description; + if (! str) str = wpt->notes; + if (str) all_points_sz += (16 + strlen(str)); + + waypt_add_to_bounds(&bds, wpt); +} + + +static int +compare_wpt_cb(const queue *a, const queue *b) +{ + const waypoint *wa = (waypoint *) a; + const waypoint *wb = (waypoint *) b; + + return strcmp(wa->shortname, wb->shortname); +} + + +static void +write_data(const char *group) +{ + queue *elem, *tmp; + int size; + + size = all_points_sz; + size += 8; + size += strlen(group); + + size += 12 + 23; + + /* --- group header --- */ + + gbfputint32(0x80009, fout); + gbfputint32(size + BMP_SIZE + 8, fout); + gbfputint32(size, fout); + + gbfputint32(strlen(group) + 4, fout); + write_string(group); + + /* --- list header --- */ + + gbfputint32(0x80008, fout); + gbfputint32(all_points_sz + 23, fout); + gbfputint32(23, fout); + + gbfputint32(GPS_Math_Deg_To_Semi(bds.max_lat), fout); + gbfputint32(GPS_Math_Deg_To_Semi(bds.max_lon), fout); + gbfputint32(GPS_Math_Deg_To_Semi(bds.min_lat), fout); + gbfputint32(GPS_Math_Deg_To_Semi(bds.min_lon), fout); + + gbfputc(0, fout); /* three unknown bytes */ + gbfputc(0, fout); /* ? should be zero ? */ + gbfputc(0, fout); + + gbfputint32(0x1000100, fout); /* ? const 0x1000100 ? */ + + QUEUE_FOR_EACH(&wptq, elem, tmp) { + char *str; + int s0, s1; + waypoint *wpt = (waypoint *)elem; + + str = wpt->description; + if (! str) str = wpt->notes; + + /* --- poi header --- */ + gbfputint32(0x80002, fout); + + s0 = s1 = 19 + strlen(wpt->shortname); + s0 += 10; /* tag(4) */ + if (str) s0 += (16 + strlen(str)); /* descr */ + + if (wpt->extra_data) + s0 += 22 + strlen((char *)wpt->extra_data); + + gbfputint32(s0, fout); /* size of following data (tag) */ + gbfputint32(s1, fout); /* basic size (without options) */ + + gbfputint32(GPS_Math_Deg_To_Semi(wpt->latitude), fout); + gbfputint32(GPS_Math_Deg_To_Semi(wpt->longitude), fout); + + gbfputint16(1, fout); /* ? always 1 ? */ + gbfputc(1, fout); /* seems to be 1 when extra options present */ + /* because we always write tag(4) this should be okay */ + + gbfputint32(strlen(wpt->shortname) + 4, fout); + write_string(wpt->shortname); + + gbfputint32(4, fout); /* tag(4) */ + gbfputint32(2, fout); + if (opt_hide) gbfputint16(1, fout); /* values != 0 hides the bitmap */ + else gbfputint16(0, fout); + + if (str) { + gbfputint32(0xa, fout); + gbfputint32(strlen(str) + 8, fout); + gbfputint32(strlen(str) + 4, fout); + write_string(str); + } + + str = (char *)wpt->extra_data; + if (str) { + gbfputint32(0x8000b, fout); + gbfputint32(strlen(str) + 10, fout); + gbfputint32(0x2, fout); + gbfputint16(0x10, fout); /* 0x10 = StreetAddress */ + gbfputint32(strlen(str) + 4, fout); + write_string(str); + xfree(str); + } + } + + if (BMP_SIZE) { + gbfputint32(5, fout); /* simple bitmap / GPSBabel logo */ + gbfputint32(BMP_SIZE, fout); + gbfwrite(gpi_bitmap, 1, BMP_SIZE, fout); + } + + gbfputc(-1, fout); /* end of group */ + gbfputint32(0xff, fout); /* final tag */ + + gbfputint16(0, fout); + gbfputc(0, fout); +} + /******************************************************************************* * %%% global callbacks called by gpsbabel main process %%% * @@ -374,16 +683,57 @@ garmin_poi_rd_init(const char *fname) { char cp[8]; - fin = gbfopen(fname, "r", MYNAME); + fin = gbfopen_le(fname, "rb", MYNAME); dt = xcalloc(1, sizeof(*dt)); read_header(); - if ((dt->codepage >= 1250) && (dt->codepage <= 1257)) { - snprintf(cp, sizeof(cp), "CP%d", dt->codepage); + if ((codepage >= 1250) && (codepage <= 1257)) { + snprintf(cp, sizeof(cp), "CP%d", codepage); cet_convert_init(cp, 1); } - else warning(MYNAME ": Unsupported code page (%d).\n", dt->codepage); + else warning(MYNAME ": Unsupported code page (%d).\n", codepage); +} + + +static void +garmin_poi_wr_init(const char *fname) +{ + char cp[8]; + cet_cs_vec_t *vec; + int i; + + fout = gbfopen_le(fname, "wb", MYNAME); + + QUEUE_INIT(&wptq); + all_points_sz = 0; + waypt_init_bounds(&bds); + + short_h = mkshort_new_handle(); + + setshort_length(short_h, 1024); + setshort_badchars(short_h, "\r\n"); + setshort_mustupper(short_h, 0); + setshort_mustuniq(short_h, 1); + setshort_whitespace_ok(short_h, 1); + setshort_repeating_whitespace_ok(short_h, 0); + setshort_defname(short_h, "POI"); + + codepage = 0; + + for (i = 1250; i <= 1257; i++) { + snprintf(cp, sizeof(cp), "CP%d", i); + vec = cet_find_cs_by_name(cp); + if (vec == global_opts.charset) { + codepage = i; + break; + } + } + + if (! codepage) { + warning(MYNAME ": Unsupported character set (%s)!\n", global_opts.charset_name); + fatal(MYNAME ": Valid values are CP1250 to CP1257.\n"); + } } @@ -397,6 +747,20 @@ garmin_poi_rd_deinit(void) } +static void +garmin_poi_wr_deinit(void) +{ + queue *elem, *tmp; + + mkshort_del_handle(&short_h); + QUEUE_FOR_EACH(&wptq, elem, tmp) { + dequeue(elem); + waypt_free((waypoint *) elem); + } + gbfclose(fout); +} + + static void garmin_poi_read(void) { @@ -405,29 +769,41 @@ garmin_poi_read(void) tag = gbfgetint32(fin); if ((tag == 0xffff) || (tag == 0xff)) return; - if (! read_tag("garmin_poi_read", tag, NULL)) return; }; } + +static void +garmin_poi_write(void) +{ + if (strlen(opt_cat) == 0) fatal(MYNAME ": Can't write empty category!\n"); + + write_header(); + waypt_disp_all(enum_waypt_cb); + sortqueue(&wptq, compare_wpt_cb); + + write_data(opt_cat); +} + /**************************************************************************/ ff_vecs_t garmin_poi_vecs = { ff_type_file, { - ff_cap_read | ff_cap_none /* waypoints */, + ff_cap_read | ff_cap_write /* waypoints */, ff_cap_none /* tracks */, ff_cap_none /* routes */ }, garmin_poi_rd_init, - NULL, /* garmin_poi_wr_init, */ + garmin_poi_wr_init, garmin_poi_rd_deinit, - NULL, /* garmin_poi_wr_deinit, */ + garmin_poi_wr_deinit, garmin_poi_read, - NULL, /* garmin_poi_write, */ + garmin_poi_write, NULL, garmin_poi_args, - CET_CHARSET_MS_ANSI, 0 + CET_CHARSET_MS_ANSI, 0 /* WIN-CP1252 */ }; /**************************************************************************/ -- 2.30.2